package com.example.glideprogressiveloadimg.memory;

import android.util.Log;

import com.example.glideprogressiveloadimg.util.Preconditions;

import java.io.IOException;
import java.io.InputStream;

public class PooledByteArrayBufferedInputStream  extends InputStream {

    private static final String TAG = "PooledByteInputStream";

    private final InputStream mInputStream;
    private byte[] mByteArray;
//    private final ResourceReleaser<byte[]> mResourceReleaser;

    /** how many bytes in mByteArray were set by last call to mInputStream.read */
    private int mBufferedSize;
    /**
     * position of next buffered byte in mByteArray to be read
     *
     * <p>invariant: 0 <= mBufferOffset <= mBufferedSize
     */
    private int mBufferOffset;

    private boolean mClosed;

//    public PooledByteArrayBufferedInputStream(InputStream inputStream, byte[] byteArray, ResourceReleaser<byte[]> resourceReleaser) {
    public PooledByteArrayBufferedInputStream(InputStream inputStream, byte[] byteArray) {
        mInputStream = Preconditions.checkNotNull(inputStream);
        mByteArray = Preconditions.checkNotNull(byteArray);
//        mResourceReleaser = Preconditions.checkNotNull(resourceReleaser);
        mBufferedSize = 0;
        mBufferOffset = 0;
        mClosed = false;
    }

    @Override
    public int read() throws IOException {
        Preconditions.checkState(mBufferOffset <= mBufferedSize);
        ensureNotClosed();
        if (!ensureDataInBuffer()) {
            return -1;
        }

        return mByteArray[mBufferOffset++] & 0xFF;
    }

    @Override
    public int read(byte[] buffer, int offset, int length) throws IOException {
        Preconditions.checkState(mBufferOffset <= mBufferedSize);
        ensureNotClosed();
        if (!ensureDataInBuffer()) {
            return -1;
        }

        final int bytesToRead = Math.min(mBufferedSize - mBufferOffset, length);
        System.arraycopy(mByteArray, mBufferOffset, buffer, offset, bytesToRead);
        mBufferOffset += bytesToRead;
        return bytesToRead;
    }

    @Override
    public int available() throws IOException {
        Preconditions.checkState(mBufferOffset <= mBufferedSize);
        ensureNotClosed();
        return mBufferedSize - mBufferOffset + mInputStream.available();
    }

    @Override
    public void close() throws IOException {
        if (!mClosed) {
            mClosed = true;
//            mResourceReleaser.release(mByteArray);
            mByteArray = null;
            super.close();
        }
    }

    @Override
    public long skip(long byteCount) throws IOException {
        Preconditions.checkState(mBufferOffset <= mBufferedSize);
        ensureNotClosed();
        final int bytesLeftInBuffer = mBufferedSize - mBufferOffset;
        if (bytesLeftInBuffer >= byteCount) {
            mBufferOffset += byteCount;
            return byteCount;
        }

        mBufferOffset = mBufferedSize;
        return bytesLeftInBuffer + mInputStream.skip(byteCount - bytesLeftInBuffer);
    }

    /**
     * Checks if there is some data left in the buffer. If not but buffered stream still has some data
     * to be read, then more data is buffered.
     *
     * @return false if and only if there is no more data and underlying input stream has no more data
     *     to be read
     * @throws IOException
     */
    private boolean ensureDataInBuffer() throws IOException {
        if (mBufferOffset < mBufferedSize) {
            return true;
        }

        final int readData = mInputStream.read(mByteArray);
        if (readData <= 0) {
            return false;
        }

        mBufferedSize = readData;
        mBufferOffset = 0;
        return true;
    }

    private void ensureNotClosed() throws IOException {
        if (mClosed) {
            throw new IOException("stream already closed");
        }
    }

    @Override
    protected void finalize() throws Throwable {
        if (!mClosed) {
            Log.e(TAG, "Finalized without closing");
            close();
        }
        super.finalize();
    }

}
